# include <argp.h>
# include "parse_utils.H"

# include <stdio.h>
//# include <stdlib.h>
# include <cmath>

# include <iostream>
# include <fstream>

# include <tpl_dynArray.H>

# include <parse_str.H>
# include <pre.H>
# include <mode.H>

# include <tpl_dynMapTree.H>
# include <tpl_dynSetTree.H>

DynSetTreap<std::string> medianaProm, medianaIndice;

// Funcion para Extraer el Codigo de la Carrera Ubicada en la posicion 3
// en el archivo de carreras de la fase 1
std::string extraer_cod_carrera(const std::string &l)
{
    size_t  pos = l.find(',',l.find(',',0)+1);
    return l.substr(pos+1, l.find(',',pos+1) -pos -1);
}

// Funcion para Extraer la subcadena que tiene la lista de carreras en el
// archivo de estudiantes de la fase 1
std::string extraer_codigos(const std::string &l)
{
    size_t ipos = 0, lpos, pos = 0;
    for(int i=0;i<=7;++i)
    {
        lpos = l.find(',', ipos);
        ipos = lpos + 1;
    }
    return l.substr(lpos+1, l.size());
}

// Funcion para Extraer la subcadena que tiene la lista de carreras del archivo
// ya concatenado (sirve para n carreras)
std::string extraer_codigos_concatenado(const std::string &l)
{
    return l.substr(l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1, l.size()-(l.size()-l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1))-l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1);
}

// Funcion para Conseguir posiciones de datos importantes para el preprocesamiento en la fase 2
size_t extraer_posicion(const std::string &l, short bandera)
{
	size_t pos;
	switch (bandera)
    {
    case 0:
		/*ID ESTUDIANTE bandera '0' */
		pos=0;
		return pos;
    	break;
    case 1:
		/*ID CARRERA bandera '1' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
    case 2:
		/*NIVEL SOCIOECONOMICO bandera '2' */
		pos = l.find(',',l.find(',',0)+1);
		if (pos!=string::npos)
			return pos;
      	break;
    case 3:
		/*UNIVERSIDAD bandera '3' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
    case 4:
		/*NOMBRE CARRERA bandera '4' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
    case 5:
	/*PROMEDIO bandera '5' */
		pos = l.find(',');
		if (pos!=string::npos)
			return pos;
      	break;
    case 6:
	/*INDICE ACADEMICO bandera '6' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
    case 7:
	/*N INTENTOS UNIVERSIDAD bandera '7' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1);	
		if (pos!=string::npos)
			return pos;
      	break;
    case 8:
	/*CONDICION ESPECIAL bandera '8' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1);	
		if (pos!=string::npos)
			return pos;
      	break;
	case 10:
	/*COORDENADA X bandera '10' */
		pos = l.find(',',l.find(',',l.find(',',0)+1)+1);	
		if (pos!=string::npos)
			return pos;
      	break;
	case 11:
	/*COORDENADA Y bandera '11' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1);	
		if (pos!=string::npos)
			return pos;
      	break;
	case 12:
	/*COORDENADA CARRERA X bandera '12' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
	case 13:
	/*COORDENADA CARRERA Y bandera '13' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
	case 14:
	/*INDICE UNIVERSIDAD bandera '14' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
	case 15:
	/*DURACION PROMEDIO bandera '15' */
		pos = l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',l.find(',',0)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1)+1);
		if (pos!=string::npos)
			return pos;
      	break;
    
	default:
	   break;
    }
}

// Funcion para extraer subString a partir de sus posiciones
std::string extraer_subString(const std::string &l, const size_t &pos){
	return l.substr(pos+1, l.find(',',pos+1) -pos -1);
}
// Funcion para extraer subString del ID del bachiller
std::string extraer_subStringID(const std::string &l,const size_t &pos){
	return l.substr(pos, l.find(',',pos+1) -pos);
}
// Funcion para encontrar que filtro resulto correcto en cada linea 
int filtro_aplicar(std::string &l, int &filtro, size_t &pos, std::string &f){
	std::string rangoA="", rangoB="";
	if (filtro==0 || filtro==5 || filtro==6 || filtro==9){
		rangoA = f.substr(0,f.find(','));
	    rangoB = f.substr(f.find(',')+1,f.length());
	}
	if (filtro==0){
		if (rangoA=="-"){
			if (atof(extraer_subStringID(l,extraer_posicion(l, ID)).c_str())<= atof(rangoB.c_str())){
				return 0;
			}
		}
		if (rangoB=="-"){
			if (atof(extraer_subStringID(l,extraer_posicion(l, ID)).c_str())>= atof(rangoA.c_str())){
				return 0;
			}
		}
		else {
			if (atof(extraer_subStringID(l,extraer_posicion(l, ID)).c_str())>= atof(rangoA.c_str()) && atof(extraer_subStringID(l,extraer_posicion(l, ID)).c_str())<= atof(rangoB.c_str())){
				return 0;
			}
		}
	}
	if (filtro==1){
		if (extraer_subString(l,extraer_posicion(l, IDCARRERA))==f){
			return 1;
		}
	}
	if (filtro==2){
		if (extraer_subString(l,extraer_posicion(l, NS))==f){
			return 2;
		}
	}
	if (filtro==3){
		if (extraer_subString(l,extraer_posicion(l, UNI))==f){
			return 3;
		}
	}
	if (filtro==4){
		if (extraer_subString(l,extraer_posicion(l, NONBRECARRERA))==f){
			return 4;
		}
	}
	if (filtro==5){
		if (rangoA=="-"){
			if (atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str())<= atof(rangoB.c_str())){
				return 5;
			}
		}
		if (rangoB=="-"){
			if (atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str())>= atof(rangoA.c_str())){
				return 5;
			}
		}
		else {
			if (atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str())>= atof(rangoA.c_str()) && atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str())<= atof(rangoB.c_str())){
				return 5;
			}
		}
	}
	if (filtro==6){
		if (rangoA=="-"){
			if (atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str())<= atof(rangoB.c_str())){
				return 6;
			}
		}
		if (rangoB=="-"){
			if (atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str())>= atof(rangoA.c_str())){
				return 6;
			}
		}
		else {
			if (atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str())>= atof(rangoA.c_str()) && atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str())<= atof(rangoB.c_str())){
				return 6;
			}
		}
	}
	if (filtro==7){
		if (extraer_subString(l,extraer_posicion(l, N))==f){
			return 7;
		}
	}
	if (filtro==8){
		if (extraer_subString(l,extraer_posicion(l, CE))==f){
			return 8;
    	}
	}
	if (filtro==9){
		if (rangoA=="-"){
			if (atof(extraer_subString(l,extraer_posicion(l, N)).c_str())<= atof(rangoB.c_str())){
    			return 9;
			}
		}
		if (rangoB=="-"){
			if (atof(extraer_subString(l,extraer_posicion(l, N)).c_str())>= atof(rangoA.c_str())){
				return 9;
			}
		}
		else {
			if (atof(extraer_subString(l,extraer_posicion(l, N)).c_str())>= atof(rangoA.c_str()) && atof(extraer_subString(l,extraer_posicion(l, N)).c_str())<= atof(rangoB.c_str())){
				return 9;
			}
		}
	}	
	else
		return 20;
}

// Funcion para calcular el Costo de asignación de cupo por bachiller
float costoAsignacionCupoBachiller(const std::string &l){
    float ns;
	if(extraer_subString(l,extraer_posicion(l, NS))=="A"){
		 ns= 0.05;	
	}
	if(extraer_subString(l,extraer_posicion(l, NS))=="B"){
		 ns= 0.1;	
	}
	if(extraer_subString(l,extraer_posicion(l, NS))=="C"){
		 ns= 0.1;	
	}
	if(extraer_subString(l,extraer_posicion(l, NS))=="D"){
		 ns= 0.45;	
	}
	if(extraer_subString(l,extraer_posicion(l, NS))=="E"){
		 ns= 0.3;	
	}
	return (ALFA/atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str()))+
		   (BETA/(ns))+
		   (GAMMA*(sqrt(pow((atof(extraer_subString(l,extraer_posicion(l, COR1)).c_str())-atof(extraer_subString(l,extraer_posicion(l, CORCA1)).c_str())),2)+pow((atof(extraer_subString	(l,extraer_posicion(l, COR2)).c_str())-atof(extraer_subString(l,extraer_posicion(l, CORCA2)).c_str())),2))))+
		   (DELTA*atoi(extraer_subString(l,extraer_posicion(l, CE)).c_str()))+
		   (EPSILON/atoi(extraer_subString(l,extraer_posicion(l, N)).c_str()))+
		   (THETA*(atof(extraer_subString(l,extraer_posicion(l, INDICEUNI)).c_str())-atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str())))+
		   (KAPPA*(atof(extraer_subString(l,extraer_posicion(l, DURACIONPROM)).c_str())/12));	 
}

// Funcion para calcular el Costo de asignación de cupo por bachiller mas la prioridad que el bachiller le dio
float costoAsignacionCupoBachillerPrioridad(const std::string &l){
	DynArray<std::string> a;
	parse_str(extraer_codigos_concatenado(l),',',a);
	int Prio;
	for(int i=0;i<a.size();++i)
    {	
		if(extraer_subString(l,extraer_posicion(l, IDCARRERA))==*(a.test(i))){
			Prio= i+1;	
		}
	}
	return costoAsignacionCupoBachiller(l)+(LAMBDA*Prio);
}

struct MedianaProm
{   
	int numeroDatos;
    
	MedianaProm(const int &numeroDatos1)
    {
		numeroDatos=numeroDatos1;
    }
    void operator()(const std::string &l)
    {
        if (count_men == (int)(numeroDatos/2)){
            mediana_prom = l;
            return;
        }
        count_men++;
    }
    ~MedianaProm(){
        medianaProm.empty();
    }
};

struct MedianaIndice
{   
	int numeroDatos;
    
	MedianaIndice(const int &numeroDatos1)
    {
		numeroDatos=numeroDatos1;
    }
    void operator()(const std::string &l)
    {
        if (count_men == (int)(numeroDatos/2)){
            mediana_indice = l;
            return;
        }
        count_men++;
        return;
    }
    ~MedianaIndice(){
        medianaIndice.empty();
    }
};


struct Desviacion
{   
	float mediaProm,mediaIndice;
	int numeroDatos;
	Desviacion(const float &mediaProm1,const float &mediaIndice1,const int &numeroDatos1)
    {
		mediaProm=mediaProm1;
		mediaIndice=mediaIndice1;
		numeroDatos=numeroDatos1;
    }
    void operator()(const std::string &l)
    {
    	desviProm = desviProm +pow((atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str())-mediaProm),2);
        medianaProm.insert(extraer_subString(l,extraer_posicion(l, PROM)).c_str());
		desviIndice= desviIndice +pow((atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str())-mediaIndice),2);
        medianaIndice.insert(extraer_subString(l,extraer_posicion(l, INDICE)).c_str());
        
    }
};

struct Op
{
    DynMapTreap<std::string,std::string> &map;
    DynArray<std::string> a;
    std::ofstream f;

    Op(DynMapTreap<std::string,std::string> &map,const std::string &salida):map(map)
    {
        f.open(salida);
    }
    
    void operator()(const std::string &l)
    {
        parse_str(extraer_codigos(l),',',a);
        
        for(int i=0;i<a.size();++i)
        {
            std::string *c = map.search(*(a.test(i)));
            if(c != 0){
                f<<l<<","<<*c<<std::endl;
            }
        }
    }
    
    ~Op()
    {
        f.close();
    }
};

struct Op2
{
    std::ofstream f;

    Op2(const std::string &salida)
    {
        f.open(salida);
    }
    
    void operator()(const std::string &l)
    {		
        f<<l<<","<<costoAsignacionCupoBachiller(l)<<","<<costoAsignacionCupoBachillerPrioridad(l)<<std::endl;
    }
    
    ~Op2()
    {
        f.close();
    }
};

void fase1(){
  
  /* Declaracion de Variables*/
  DynMapTreap<std::string,std::string> map;
  DynSetTreap<std::string> tree;
    
  std::string l;
    
  ifstream estudiantes(input_file_students);
  ifstream carreras(input_file_careers);
    
  if(output_file_name.size() == 0){
    output_file_name = "pre-1.txt";
  }
   
  /* Carga de los Datos de los Estudiantes y ordenado con el treap */
  while (getline(estudiantes,l))
  {
      tree.insert(l);
  }
  estudiantes.close();
   
  /* Carga de los Datos de las Carreras en un Map */   
  while (getline(carreras,l))
  {
      map.insert(extraer_cod_carrera(l),l);
  }
    
  carreras.close();
    
  /* Recorrido Infijo del treap */
  tree.for_each<Op>(Op(map,output_file_name));
  
  map.empty();
  tree.empty();
}

void fase2(){
    
  std::string l;
  ifstream filtro(input_file_name);
  DynSetTreap<std::string> tree;
  DynSetTreap<Number_With_Frequency_Mode, CmpMode> ModeProm, ModeIndice;		

  size_t pos,count=0;
  int n[10];
  int nn=0, numeroDatos=0;
  float mediaProm =0, mediaIndice=0,varianza = 0;

  while (getline(filtro,l))
  {
    nn=0;
	for (int i=0; i<sizeof(filtros_indice)/sizeof(int);i++){
        if(filtros[i]!= ""){
            n[i]= filtro_aplicar(l, filtros_indice[i], pos, filtros[i]);
            ++count;
        }
	}
	//Comparar arreglos
    if (sizeof(filtros_indice)/sizeof(int) != count){    
        for (int i=0; i<sizeof(filtros_indice)/sizeof(int);i++){
            if(filtros[i]!= ""){
                nn= nn + (n[i] - filtros_indice[i]);
            }
        }
    }
	if (nn==0){
        
		tree.insert(l);
		//Arbol de moda Promedio
		Number_With_Frequency_Mode nf(atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str()));
		Number_With_Frequency_Mode * __nf = ModeProm.search_or_insert(nf);
		++__nf->f;
		//Arbol de moda Indice
		Number_With_Frequency_Mode nfIndice(atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str()));
		Number_With_Frequency_Mode * __nfIndice = ModeIndice.search_or_insert(nfIndice);
		++__nfIndice->f;
		//Medias
		mediaProm = mediaProm + atof(extraer_subString(l,extraer_posicion(l, PROM)).c_str());
		mediaIndice = mediaIndice + atof(extraer_subString(l,extraer_posicion(l, INDICE)).c_str());
		++numeroDatos;
	}
  }

  filtro.close();
        
  /* Recorrido Infijo del treap */
  tree.for_each<Op2>(Op2(output_file_name));
	
  //Media
  mediaProm=mediaProm/numeroDatos;
  mediaIndice=mediaIndice/numeroDatos;
  
  //Calculo de la moda
  OpMode op(Number_With_Frequency_Mode(0));
  ModeProm.for_each<OpMode>(op);
	
  OpMode opIndice(Number_With_Frequency_Mode(0));
  ModeIndice.for_each<OpMode>(opIndice);

  //Desviacion Estandar
  tree.for_each<Desviacion>(Desviacion(mediaProm,mediaIndice,numeroDatos));
  
  // Mediana
  medianaProm.for_each<MedianaProm>(MedianaProm(numeroDatos));
  medianaIndice.for_each<MedianaIndice>(MedianaIndice(numeroDatos));
  
  size_t i = numeroDatos/2;
  
  std::fstream f;
  f.open(output_file_name, fstream::out | fstream::app);
  f << mediaProm<<","<< mediana_prom<<","<<op.__nf.n<<","<<desviProm/(numeroDatos-1)<<","<<sqrt(desviProm/(numeroDatos-1))<<endl;
  f << mediaIndice<<","<< mediana_indice<<","<<opIndice.__nf.n<<","<<desviIndice/(numeroDatos-1)<<","<<sqrt(desviIndice/(numeroDatos-1))<<endl;
  f.close();
  
  tree.empty();
  ModeProm.empty();
  ModeIndice.empty();		
}
  
int main(int argc, char *argv[]) {
  
  int flag = 0;
  
  command_line = command_line_to_string(argc, argv);

  argp_parse(&argDefs, argc, argv, ARGP_IN_ORDER, 0, NULL);
  
  cout << hello;
  
  if(input_file_students.size() != 0 and input_file_careers.size() != 0){
    
    if(output_file_name.size() == 0){
      output_file_name = "pre-1.txt";
    }
    fase1();
    ++flag;
  }else{
    if(output_file_name.size() == 0){
      output_file_name = "pre-2.txt";
    }
    fase2();
    ++flag;
  }
  if(flag < 1)
    cout << "Usage: pre " <<argDoc << endl;
  else
    cout << "Done!"<< endl;
}